package com.base.lock.zk.example;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import lombok.extern.slf4j.Slf4j;

/**
 * Watcher 这个是观察器
 * @ClassName: ZkLock
 * @Description: 
 * @Author xuyou
 * @DateTime 2020年11月28日 下午7:55:44
 */
@Slf4j
public class ZkLock implements AutoCloseable,Watcher {

	private ZooKeeper zooKeeper;
	private String znode;
	
	public ZkLock() throws IOException {
		this.zooKeeper = new ZooKeeper("127.0.0.1", 100000, this);
	}
	
	/**
	 * 获取锁
	 * @Title: getLock
	 * @Description: 
	 * @Author xuyou
	 * @DateTime 2020年11月28日 下午8:03:59
	 * @param businessCode
	 * @return
	 */
	public boolean getLock(String businessCode) {
		
		try {
			//查看节点是否存在

			Stat stat = zooKeeper.exists("/"+businessCode, false);
			//不存在,则创建业务根节点
			if (stat == null) {
				//ZooDefs.Ids.OPEN_ACL_UNSAFE 不使用账号密码即可连接
				//CreateMode.EPHEMERAL 创建瞬时节点
				//CreateMode.EPHEMERAL_SEQUENTIAL 创建瞬时有序节点
				//CreateMode.PERSISTENT 创建持久节点
				//CreateMode.PERSISTENT_SEQUENTIAL 创建持久有序节点
				zooKeeper.create("/"+businessCode, businessCode.getBytes(), 
						ZooDefs.Ids.OPEN_ACL_UNSAFE,
						CreateMode.PERSISTENT);
			}
			
			//创建业务瞬时有序节点
			// order/order_0000000000
			znode = zooKeeper.create("/"+businessCode+"/"+businessCode+"_", businessCode.getBytes(), 
					ZooDefs.Ids.OPEN_ACL_UNSAFE,
					CreateMode.EPHEMERAL_SEQUENTIAL);
			//获取子节点列表
			List<String> childrenNodes = zooKeeper.getChildren("/"+businessCode, false);
			//进行升序排序 序号小的排在前面
			Collections.sort(childrenNodes);
			//获取第一个节点
			String firstNodes = childrenNodes.get(0);
			//判断是否是第一个节点,如果是则获取到锁
			if (znode.endsWith(firstNodes)) {
				return true;
			}

			//如果当前创建的节点不是第一个节点
			String lastNode = firstNodes;
			for (String node : childrenNodes) {
				//将当前创建的节点创建监听器,监听前一个节点的过期	
				if (znode.endsWith(node)) {
					zooKeeper.exists("/"+businessCode+"/"+lastNode, true);
					break;
				}else {
					lastNode = node;
				}
			}
			//使用本地锁,来保证多线程时只有一个线程释放zk的锁
			synchronized (this) {
				log.info("等待被唤醒的线程号:{}",Thread.currentThread().getId());
				wait();
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
	
	
	
	//节点消失之后会执行到这里
	@Override
	public void process(WatchedEvent event) {
		//如果当前事件是节点删除
		if (event.getType() == EventType.NodeDeleted) {
			synchronized (this) {
				log.info("唤醒其他线程的线程号:{}",Thread.currentThread().getId());
				notify();//唤起当前等待的线程 
			}
		}
	}

	@Override
	public void close() throws Exception {
		//删除节点
		zooKeeper.delete(znode, -1);
		zooKeeper.close();
		log.info("释放zk锁");
		log.info("释放线程的线程号:{}",Thread.currentThread().getId());
	}
}
